﻿using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.Globalization;

namespace Tyng.Data
{
    /// <summary>
    /// 
    /// </summary>
    public abstract class CustomDataReader : DbDataReader
    {
        private IDataRecord _currentDataRecord;
        private int _recordsAffected = -1;
        private int _maxRows = int.MaxValue;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="maxRowsToRead"></param>
        protected CustomDataReader(int maxRowsToRead)
        {
            _maxRows = maxRowsToRead;
        }

        /// <summary>
        /// 
        /// </summary>
        protected CustomDataReader()
        {
        }

        /// <summary>
        /// Implemented in sub-type to return next record
        /// </summary>
        /// <returns>IDataRecord if data exists, otherwise null</returns>
        protected abstract IDataRecord GetNextDataRecord();

        /// <summary>
        /// Returns the type of the current IDataRecord.
        /// </summary>
        public Type CurrentRecordType
        {
            get
            {
                return _currentDataRecord.GetType();
            }
        }

        /// <summary>
        /// Gets the number of rows in the data result (valid only after complete read)
        /// </summary>
        public override int RecordsAffected
        {
            get
            {
                return _recordsAffected;
            }
        }

        /// <summary>
        /// Can reset RecordsAffected count when moving between result sets.
        /// </summary>
        protected void ResetRecordsAffected()
        {
            _recordsAffected = -1;
        }

        /// <summary>
        /// The maximum number of rows to read from the source
        /// </summary>
        public int MaxRowsToRead
        {
            get
            {
                return _maxRows;
            }
        }

        /// <summary>
        /// Not implemented. 
        /// </summary>
        /// <returns>Always returns false</returns>
        public override bool NextResult()
        {
            return false;
        }

        /// <summary>
        /// Closes the StreamDataReader
        /// </summary>
        public override void Close()
        {
            _currentDataRecord = null;
        }

        /// <summary>
        /// Advances the StreamDataReader to the next record
        /// </summary>
        /// <returns>Boolean indicating whether or not the next record exists</returns>
        public override bool Read()
        {
            if (this.IsClosed)
                throw new InvalidOperationException("Data reader closed");

            if (_recordsAffected >= _maxRows)
                return false;

            //get the next record.
            _currentDataRecord = this.GetNextDataRecord();

            if (_currentDataRecord != null)
            {
                //Increment the record count
                if (_recordsAffected == -1)
                    _recordsAffected = 1;
                else
                    _recordsAffected++;

                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating the depth of nesting for the current row.
        /// </summary>
        public override int Depth
        {
            get
            {
                return 0;
            }
        }

        #region Schema Table
        DataTable _schema;

        /// <summary>
        /// Not implemented.
        /// </summary>
        /// <returns>null</returns>
        public override DataTable GetSchemaTable()
        {
            if (this.IsClosed)
                throw new InvalidOperationException();

            if (_schema == null)
            {
                _schema = CreateSchemaDataTable();

                for (int i = 0; i < DataFields.Length; i++)
                {
                    Field f = DataFields[i];
                    DataRow dr = _schema.NewRow();
                    FillSchemaRow(i, f, dr);
                    _schema.Rows.Add(dr);
                }
            }

            return _schema;
        }

        /// <summary>
        /// Fill schema table row with data for specified field
        /// </summary>
        /// <param name="ordinal">Ordinal of the field</param>
        /// <param name="f">The field</param>
        /// <param name="dr">Data row to set the data</param>
        protected virtual void FillSchemaRow(int ordinal, Field f, DataRow dr)
        {
            dr[SchemaTableColumn.AllowDBNull] = true;
            dr[SchemaTableColumn.BaseColumnName] = f.Name;
            dr[SchemaTableColumn.BaseSchemaName] = string.Empty;
            dr[SchemaTableColumn.BaseTableName] = string.Empty;
            dr[SchemaTableColumn.ColumnName] = f.Name;
            dr[SchemaTableColumn.ColumnOrdinal] = ordinal;
            dr[SchemaTableColumn.ColumnSize] = int.MaxValue;
            dr[SchemaTableColumn.DataType] = f.DataType;
            dr[SchemaTableColumn.IsAliased] = false;
            dr[SchemaTableColumn.IsExpression] = false;
            dr[SchemaTableColumn.IsKey] = false;
            dr[SchemaTableColumn.IsLong] = false;
            dr[SchemaTableColumn.IsUnique] = false;
            dr[SchemaTableColumn.NonVersionedProviderType] = DBNull.Value;
            dr[SchemaTableColumn.NumericPrecision] = DBNull.Value;
            dr[SchemaTableColumn.NumericScale] = DBNull.Value;
            dr[SchemaTableColumn.ProviderType] = DBNull.Value;
        }

        /// <remarks />
        protected virtual void AddSchemaDataColumns(DataTable dt)
        {
            dt.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
            dt.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
            dt.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
            dt.Columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
            dt.Columns.Add(SchemaTableColumn.ColumnName, typeof(string));
            dt.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
            dt.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
            dt.Columns.Add(SchemaTableColumn.DataType, typeof(Type));
            dt.Columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
            dt.Columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
            dt.Columns.Add(SchemaTableColumn.IsKey, typeof(bool));
            dt.Columns.Add(SchemaTableColumn.IsLong, typeof(bool));
            dt.Columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
            dt.Columns.Add(SchemaTableColumn.NonVersionedProviderType, typeof(int));
            dt.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
            dt.Columns.Add(SchemaTableColumn.NumericScale, typeof(short));
            dt.Columns.Add(SchemaTableColumn.ProviderType, typeof(int));
        }

        /// <remarks />
        protected virtual DataTable CreateSchemaDataTable()
        {
            DataTable table = new DataTable("SchemaTable");

            table.Locale = CultureInfo.InvariantCulture;
            table.MinimumCapacity = this.FieldCount;

            AddSchemaDataColumns(table);

            return table;
        }

        /// <remarks />
        protected abstract Field[] DataFields { get; }

        #endregion

        /// <summary>
        /// Gets the value of the specified column as an Int32
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override int GetInt32(int i)
        {
            return _currentDataRecord.GetInt32(i);
        }

        /// <summary>
        /// Get value for column with specified name
        /// </summary>
        public override object this[string name]
        {
            get
            {
                return _currentDataRecord[name];
            }
        }

        /// <summary>
        /// Get value for column at specified ordinal
        /// </summary>
        public override object this[int i]
        {
            get
            {
                return _currentDataRecord[i];
            }
        }

        /// <summary>
        /// Gets the value of the specified column as an object
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override object GetValue(int i)
        {
            return _currentDataRecord.GetValue(i);
        }

        /// <summary>
        /// Whether the specified field is set to null.
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>Boolean: true = null, false = not null</returns>
        public override bool IsDBNull(int i)
        {
            return _currentDataRecord.IsDBNull(i);
        }

        /// <summary>
        /// Gets the value of the specified column and fills the passed buffer
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <param name="fieldOffset"></param>
        /// <param name="buffer"></param>
        /// <param name="bufferoffset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            return _currentDataRecord.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
        }

        /// <summary>
        /// Gets the value of the specified column as a Byte
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override byte GetByte(int i)
        {
            return _currentDataRecord.GetByte(i);
        }

        /// <summary>
        /// Gets the .Net Type of the specified column
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The Type of the specified column</returns>
        public override Type GetFieldType(int i)
        {
            return DataFields[i].DataType;
        }

        /// <summary>
        /// Gets the value of the specified column as a Decimal
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override decimal GetDecimal(int i)
        {
            return _currentDataRecord.GetDecimal(i);
        }

        /// <summary>
        /// Gets the values of the record as an object array
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public override int GetValues(object[] values)
        {
            return _currentDataRecord.GetValues(values);
        }

        /// <summary>
        /// Gets the name for the field.
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The name of the field.</returns>
        public override string GetName(int i)
        {
            return DataFields[i].Name;
        }

        /// <summary>
        /// Number of fields
        /// </summary>
        public override int FieldCount
        {
            get
            {
                Field[] fields = DataFields;

                if (fields == null) return 0;

                return fields.Length;
            }
        }

        /// <summary>
        /// Gets the value of the specified column as an Int64
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override long GetInt64(int i)
        {
            return _currentDataRecord.GetInt64(i);
        }

        /// <summary>
        /// Gets the value of the specified column as a Double
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override double GetDouble(int i)
        {
            return _currentDataRecord.GetDouble(i);
        }

        /// <summary>
        /// Gets the value of the specified column as a Boolean
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override bool GetBoolean(int i)
        {
            return _currentDataRecord.GetBoolean(i);
        }

        /// <summary>
        /// Gets the value of the specified column as a Guid
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override Guid GetGuid(int i)
        {
            return _currentDataRecord.GetGuid(i);
        }

        /// <summary>
        /// Gets the value of the specified column as a DateTime
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override DateTime GetDateTime(int i)
        {
            return _currentDataRecord.GetDateTime(i);
        }

        /// <summary>
        /// Gets the ordinal of the column with the specified name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override int GetOrdinal(string name)
        {
            return Array.FindIndex<Field>(DataFields, delegate(Field f) { return string.CompareOrdinal(f.Name, name) == 0; });
        }

        /// <summary>
        /// Gets the String data type name of the specified column.
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns></returns>
        public override string GetDataTypeName(int i)
        {
            return DataFields[i].DataTypeName;
        }

        /// <summary>
        /// Gets the value of the specified column as a Float
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override float GetFloat(int i)
        {
            return _currentDataRecord.GetFloat(i);
        }

        /// <summary>
        /// Fills the passed char array with data from the specified column
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <param name="fieldoffset"></param>
        /// <param name="buffer"></param>
        /// <param name="bufferoffset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            return _currentDataRecord.GetChars(i, fieldoffset, buffer, bufferoffset, length);
        }

        /// <summary>
        /// Gets the value of the specified column as a String
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override string GetString(int i)
        {
            return _currentDataRecord.GetString(i);
        }

        /// <summary>
        /// Gets the value of the specified column as a Char
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override char GetChar(int i)
        {
            return _currentDataRecord.GetChar(i);
        }

        /// <summary>
        /// Gets the value of the specified column as an Int16
        /// </summary>
        /// <param name="i">The zero-based column ordinal</param>
        /// <returns>The value of the specified column</returns>
        public override short GetInt16(int i)
        {
            return _currentDataRecord.GetInt16(i);
        }

        /// <remarks/>
        public override IEnumerator GetEnumerator()
        {
            return new DbEnumerator(this, true);
        }

        /// <remarks/>
        public override bool HasRows
        {
            get { throw new NotImplementedException(); }
        }


    }
}
